package auth

import (
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	jwtUtils "github.com/golang-jwt/jwt"
	"go.uber.org/zap"
	"net/http"
	"time"
	"virtual-resource-schedule-service/global"
	"virtual-resource-schedule-service/model/common"
)

var (
	JwtTokenName      = "CHAOS-AI-TOKEN"
	RequestIdName     = "CHAOS-AI-Request-Id"
	RequestAuthIdName = "CHAOS-AI-Request-AuthId"
)

// 错误码定义
var (
	TokenExpired     = errors.New("Token is expired")
	TokenNotValidYet = errors.New("Token not active yet")
	TokenMalformed   = errors.New("That's not even a token")
	TokenInvalid     = errors.New("Couldn't handle this token:")
	// @TODO 前期用固定的authID
	StaticAuthID = "auto-ai-20241010"
)

// JWTAuth 使用Handler处理JWT的业务逻辑
func JWTAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		// TOKEN 从请求头中获取
		token := c.Request.Header.Get(JwtTokenName)
		authId := c.Request.Header.Get(RequestAuthIdName)
		requestId := c.Request.Header.Get(RequestIdName)
		if token == "" || authId == "" || requestId == "" {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code":    "10000403",
				"message": "无权限访问当前API",
				"success": false,
			})
			// 不允许访问
			c.Abort()
			return
		}
		if authId != StaticAuthID {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code":    "10000403",
				"message": "无效AuthID",
				"success": false,
			})
			// 不允许访问
			c.Abort()
			return
		}
		jwt := NewJWT()
		// 解析请求Header中带的requestID和authID
		requestClaims := common.RequestClaims{
			RequestID: requestId,
			AuthID:    authId,
		}
		// parseToken 解析token包含的信息
		claims, err := jwt.ParseToken(requestClaims, token)
		if err != nil {
			zap.S().Error(err)
			c.JSON(http.StatusUnauthorized, gin.H{
				"code":    "10000401",
				"message": "授权已过期",
			})
			c.Abort()
			return
		}
		c.Set("claims", claims)
		c.Set("authID", claims.AuthID)
		c.Next()
	}
}

type JWT struct {
	SigningKey []byte
	ExpireTime int64
}

// NewJWT 初始化JWT
func NewJWT() *JWT {
	return &JWT{
		[]byte(global.ServerConfig.JwtConfig.JwtSign),
		global.ServerConfig.JwtConfig.JwtExpire,
	}
}

// GenerateToken 创建一个token
func (jwt *JWT) GenerateToken(requestClaims common.RequestClaims) (string, error) {
	token := jwtUtils.NewWithClaims(jwtUtils.SigningMethodHS256, requestClaims)

	// 根据SigningKey + authID 获取token
	authId := requestClaims.AuthID
	zap.S().Infof("GenerateToken authId: %v", authId)
	signKey := fmt.Sprintf("%s:%s", jwt.SigningKey, authId)
	zap.S().Infof("GenerateToken signKey: %v", signKey)

	return token.SignedString([]byte(signKey))
}

// ParseToken 解析Token信息
func (jwt *JWT) ParseToken(requestClaims common.RequestClaims, tokenString string) (*common.RequestClaims, error) {
	// 根据SigningKey + authID 获取token
	authId := requestClaims.AuthID
	zap.S().Infof("ParseToken authId: %v", authId)
	signKey := fmt.Sprintf("%s:%s", jwt.SigningKey, authId)
	zap.S().Infof("ParseToken signKey: %v", signKey)

	token, err := jwtUtils.ParseWithClaims(tokenString,
		&common.RequestClaims{},
		func(token *jwtUtils.Token) (interface{}, error) {
			return []byte(signKey), nil
		})
	if err != nil {
		if ve, ok := err.(*jwtUtils.ValidationError); ok {
			if ve.Errors&jwtUtils.ValidationErrorMalformed != 0 {
				return nil, TokenMalformed
			} else if ve.Errors&jwtUtils.ValidationErrorExpired != 0 {
				// TOKEN 失效
				return nil, TokenExpired
			} else if ve.Errors&jwtUtils.ValidationErrorNotValidYet != 0 {
				return nil, TokenNotValidYet
			} else {
				return nil, TokenInvalid
			}
		}
	}
	if token != nil {
		if claims, ok := token.Claims.(*common.RequestClaims); ok && token.Valid {
			return claims, nil
		}
		return nil, TokenInvalid
	} else {
		return nil, TokenInvalid
	}
}

// RefreshToken 刷新token
func (jwt *JWT) RefreshToken(requestClaims common.RequestClaims, tokenString string) (string, error) {
	jwtUtils.TimeFunc = func() time.Time {
		return time.Unix(0, 0)
	}

	authId := requestClaims.AuthID
	zap.S().Infof("ParseToken authId: %v", authId)
	signKey := fmt.Sprintf("%s:%s", jwt.SigningKey, authId)
	zap.S().Infof("ParseToken signKey: %v", signKey)

	token, err := jwtUtils.ParseWithClaims(
		tokenString,
		&common.RequestClaims{},
		func(token *jwtUtils.Token) (interface{}, error) {
			return []byte(signKey), nil
		})
	if err != nil {
		return "", err
	}
	if claims, ok := token.Claims.(*common.RequestClaims); ok && token.Valid {
		jwtUtils.TimeFunc = time.Now
		expire := global.ServerConfig.JwtConfig.JwtExpire * 1000 * 1000 * 1000
		claims.StandardClaims.ExpiresAt = time.Now().Add(time.Duration(expire)).Unix()
		return jwt.GenerateToken(*claims)
	}
	return "", TokenInvalid
}
